package com.yy.eventbus;

import android.os.Handler;
import android.os.Looper;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * User: gongyongfeng2@yy.com
 * Date: 2020/7/22 20 :43
 */
public class YYEventBus {

    private Map<Object, List<SubscriberMethod>> mCacheMap = new HashMap<>();
    private Handler mHandler;
    private ExecutorService mExecutorService;

    private YYEventBus() {
        mHandler = new Handler(Looper.getMainLooper());
        mExecutorService = Executors.newCachedThreadPool();
    }

    public static YYEventBus getDefault() {
        return YYEventBusHolder.INSTANCE;
    }

    public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = mCacheMap.get(subscriber);
        if (subscriberMethods == null) {
            subscriberMethods = getSubscriberMethod(subscriber);
            mCacheMap.put(subscriber, subscriberMethods);
        }
    }

    private List<SubscriberMethod> getSubscriberMethod(Object subscriber) {
        List<SubscriberMethod> subscriberMethods = new ArrayList<>();
        Class<?> subscriberClass = subscriber.getClass();
        while (subscriberClass != null) {
            String name = subscriberClass.getName();
            if (name.startsWith("java.") || name.startsWith("javax.") ||
                    name.startsWith("android.") || name.startsWith("androidx.")) {
                break;
            }

            Method[] methods = subscriberClass.getDeclaredMethods();
            for (Method method : methods) {
                YYSubscriber annotation = method.getAnnotation(YYSubscriber.class);
                if (annotation == null) {
                    continue;
                }
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length != 1) {
                    throw new IllegalArgumentException("arguments must be have only one");
                }
                YYThreadModel threadModel = annotation.threadMode();

                SubscriberMethod subscriberMethod = new SubscriberMethod(method, threadModel,
                        parameterTypes[0]);

                subscriberMethods.add(subscriberMethod);
            }
            subscriberClass = subscriberClass.getSuperclass();
        }
        return subscriberMethods;
    }

    public void post(final Object sendObj) {
        for (Map.Entry<Object, List<SubscriberMethod>> entry : mCacheMap.entrySet()) {
            List<SubscriberMethod> subscriberMethods = entry.getValue();
            final Object name = entry.getKey();
            for (final SubscriberMethod subscriberMethod : subscriberMethods) {
                //接受事件
                if (subscriberMethod.getParameterType().isAssignableFrom(sendObj.getClass())) {
                    switch (subscriberMethod.getYYThreadModel()) {
                        case MAIN:
                            if (Looper.myLooper() == Looper.getMainLooper()) {
                                invoke(subscriberMethod, name, sendObj);
                            } else {
                                mHandler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        invoke(subscriberMethod, name, sendObj);
                                    }
                                });
                            }
                            break;
                        case ASYNC:
                            if (Looper.myLooper() == Looper.getMainLooper()) {
                                mExecutorService.execute(new Runnable() {
                                    @Override
                                    public void run() {
                                        invoke(subscriberMethod, name, sendObj);
                                    }
                                });
                            } else {
                                invoke(subscriberMethod, name, sendObj);
                            }
                            break;

                        case POSTING:
                            invoke(subscriberMethod, name, sendObj);
                            break;
                    }
                }
            }
        }
    }

    public void unRegister(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = mCacheMap.get(subscriber);
        if (subscriberMethods != null) {
            mCacheMap.remove(subscriber);
        }
    }

    private void invoke(SubscriberMethod subscriberMethod, Object name, Object sendObj) {
        Method method = subscriberMethod.getSubscriberMethod();
        try {
            method.invoke(name, sendObj);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static class YYEventBusHolder {
        private static final YYEventBus INSTANCE = new YYEventBus();
    }
}
